﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.Win32;

namespace WindowsEventLogManager
{
    public class EventLogInfo : IDisposable
    {
        #region Constants

        public const string EventLogRegistryKey = "SYSTEM\\CurrentControlSet\\Services\\Eventlog";

        #endregion

        #region Fields

        private EventLog m_EventLog;
        private List<EventSourceInfo> m_Sources;

        #endregion

        #region Constructors

        public EventLogInfo(EventLog log)
        {
            this.m_Sources = new List<EventSourceInfo>();

            this.EventLog = log;
        }

        #endregion

        #region Properties

        [Browsable(false)]
        public EventLog EventLog
        {
            get
            {
                return this.m_EventLog;
            }
            private set
            {
                this.m_EventLog = value;

                this.ResolveEventLogRegistryKey();

                this.ResolveEventSources();
            }
        }

        [Browsable(false)]
        public RegistryKey ParentRegistryKey { get; private set; }

        [Browsable(true)]
        public RegistryKey RegistryKey { get; private set; }

        [Browsable(true)]
        public RegistryKey ChildRegistryKey { get; private set; }

        [Browsable(true)]
        public string DisplayName
        {
            get
            {
                return this.EventLog.LogDisplayName;
            }
        }

        [Browsable(true)]
        public string MachineName
        {
            get
            {
                return this.EventLog.MachineName;
            }
        }

        [Browsable(true)]
        [DefaultValue(512L)]
        public long MaximumKilobytes
        {
            get
            {
                return this.EventLog.MaximumKilobytes;
            }
            set
            {
                this.EventLog.MaximumKilobytes = value;
            }
        }

        [Browsable(true)]
        [DefaultValue(false)]
        public bool EnableRaisingEvents
        {
            get
            {
                return this.EventLog.EnableRaisingEvents;
            }
            set
            {
                this.EventLog.EnableRaisingEvents = value;
            }
        }

        [Browsable(true)]
        public string Log
        {
            get
            {
                return this.EventLog.Log;
            }
            set
            {
                Rename(value);
            }
        }

        [Browsable(true)]
        [DefaultValue(OverflowAction.OverwriteOlder)]
        public OverflowAction OverflowAction
        {
            get
            {
                return this.EventLog.OverflowAction;
            }
            set
            {
                int retentionDays = this.MinimumRetentionDays;

                switch (value)
                {
                    case OverflowAction.DoNotOverwrite:
                        {
                            retentionDays = -1;
                            break;
                        }
                    case OverflowAction.OverwriteAsNeeded:
                        {
                            retentionDays = 0;
                            break;
                        }
                    case OverflowAction.OverwriteOlder:
                        {
                            if (retentionDays < 1)
                            {
                                retentionDays = 1;
                            }
                            break;
                        }
                }

                this.EventLog.ModifyOverflowPolicy(value, retentionDays);
            }
        }

        [Browsable(true)]
        [DefaultValue(7)]
        public int MinimumRetentionDays
        {
            get
            {
                return this.EventLog.MinimumRetentionDays;
            }
            set
            {
                this.EventLog.ModifyOverflowPolicy(this.OverflowAction, value);
            }
        }

        [Browsable(true)]
        public bool IsDefaultWindowsLog
        {
            get
            {
                bool isSystem = false;

                if (string.Equals(this.Log, "System", StringComparison.Ordinal) ||
                    string.Equals(this.Log, "Security", StringComparison.Ordinal) ||
                    string.Equals(this.Log, "Application", StringComparison.Ordinal))
                {
                    isSystem = true;
                }

                return isSystem;
            }
        }

        [Browsable(true)]
        public bool CanBeRenamed
        {
            get
            {
                return ((this.RegistryKey != null) && (this.ChildRegistryKey != null));
            }
        }

        [Browsable(true)]
        public List<EventSourceInfo> Sources
        {
            get
            {
                return this.m_Sources;
            }
        }

        #endregion

        #region Events

        public event EventHandler<EventLogRenamedEventArgs> EventRenamed;

        protected virtual void OnEventRenamed(EventLogRenamedEventArgs args)
        {
            if (this.EventRenamed != null)
            {
                this.EventRenamed(this, args);
            }
        }

        #endregion

        #region Methods

        private void ResolveEventLogRegistryKey()
        {
            this.ParentRegistryKey = Registry.LocalMachine.OpenSubKey(EventLogInfo.EventLogRegistryKey, true);

            this.RegistryKey = this.ParentRegistryKey.OpenSubKey(this.Log, true);

            this.ResolveEventLogChildRegistryKey();
        }

        private void ResolveEventLogChildRegistryKey()
        {
            if (this.RegistryKey.SubKeyCount > 0)
            {
                this.ChildRegistryKey = this.RegistryKey.OpenSubKey(this.DisplayName, true);
            }
        }

        private void ResolveEventSources()
        {
            string[] values = this.RegistryKey.GetValue("Sources") as string[];

            if (values != null)
            {
                foreach (string value in values)
                {
                    this.m_Sources.Add(new EventSourceInfo(this, value));
                }
            }
        }

        public void Delete()
        {
            if (!this.IsDefaultWindowsLog)
            {
                EventLog.Delete(this.Log, this.MachineName);
            }
        }

        public void Clear()
        {
            this.EventLog.Clear();
        }

        public void MoveLogFile()
        {
            //TODO: Implement.
        }

        public void Rename(string name)
        {
            if (!this.IsDefaultWindowsLog && this.CanBeRenamed)
            {
                string displayName = this.DisplayName;
                string oldName = this.Log;

                this.ChildRegistryKey = this.RenameKey(name, displayName, this.RegistryKey, this.ChildRegistryKey);

                this.RegistryKey = this.RenameKey(name, oldName, this.ParentRegistryKey, this.RegistryKey);

                this.EventLog = new EventLog(name, Environment.MachineName);

                EventLogRenamedEventArgs args = new EventLogRenamedEventArgs();
                args.NewName = name;
                args.OldName = oldName;

                this.OnEventRenamed(args);
            }
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.EventLog != null)
                {
                    this.EventLog.Dispose();
                }

                this.ParentRegistryKey.Close();
                this.RegistryKey.Close();
            }
        }

        private RegistryKey RenameKey(string newKeyName, string originalKeyName, RegistryKey parentKey, RegistryKey sourceKey)
        {
            RegistryKey destinationKey = parentKey.CreateSubKey(newKeyName, RegistryKeyPermissionCheck.ReadWriteSubTree);

            this.RenameKey(sourceKey, destinationKey);

            try
            {
                parentKey.DeleteSubKeyTree(originalKeyName);
            }
            catch
            {
                parentKey.DeleteSubKeyTree(newKeyName);

                throw;
            }

            return destinationKey;
        }

        private void RenameKey(RegistryKey sourceKey, RegistryKey destinationKey)
        {
            foreach (string valueName in sourceKey.GetValueNames())
            {
                object objValue = sourceKey.GetValue(valueName);
                RegistryValueKind valKind = sourceKey.GetValueKind(valueName);
                destinationKey.SetValue(valueName, objValue, valKind);
            }

            foreach (string sourceSubKeyName in sourceKey.GetSubKeyNames())
            {
                RegistryKey sourceSubKey = sourceKey.OpenSubKey(sourceSubKeyName);
                RegistryKey destSubKey = destinationKey.CreateSubKey(sourceSubKeyName);

                this.RenameKey(sourceSubKey, destSubKey);
            }

            destinationKey.Flush();
        }

        #endregion
    }
}
